/* 
 * File:   EEPROM_25LC256.cpp
 * Author: ejordan
 *
 * Created on March 14, 2013, 12:33 AM
 */

#include <p32xxxx.h>
#include <plib.h>
#include "Eeprom.h"
#include "Spi.h"
// Constructors

/*! \brief Initializes a new instance of the EEPROM class.
 *
 * Initializes a new instance of the EEPROM class.
 *
 * @param spi Pointer to the SPI device to use for EEPROM communications.
 */
Eeprom::Eeprom(Spi *spi)
{
	this->_spi = spi;
	return;
}


// Destructor

/*! \brief Eeprom destructor
 */
Eeprom::~Eeprom()
{
	return;
}


// Methods

/*! \brief Reads and returns the STATUS register from the EEPROM
 *
 * @returns STATUS Register
 */
unsigned char Eeprom::ReadStatus()
{
	unsigned char data;
	
	_spi->ChipSelect(true); // Assert !CS
	_spi->Write((unsigned char) Eeprom::Commands::ReadStatusRegister); // Read Status Register
	data = _spi->Read(); // Read Byte
	_spi->ChipSelect(false); // Assert !CS
	return data;
	
}

/*! \brief Writes a single byte to the EEPROM at the specified address
 *
 * @param address Address to write byte to
 * @param data Data byte to send
 */
void Eeprom::Write(UINT32 address, unsigned char data)
{
	this->Write(address, &data, 0, 1);
}

/*! \brief Writes multiple bytes to the EEPROM starting at the specified address
 *
 * @param address Address to write byte to
 * @param buffer Pointer to the buffer of the data to send.
 * @param length Number of bytes to send.
 */
void Eeprom::Write(UINT32 address, const unsigned char *buffer, int length)
{
	this->Write(address, buffer, 0, length);
	return;
}

/*! \brief Writes multiple bytes to the EEPROM starting at the specified address
 *
 * @param address Address to write byte to
 * @param buffer Pointer to the buffer of the data to send.
 * @param index Position in array to start sending.
 * @param length Number of bytes to send.
 */
void Eeprom::Write(UINT32 address, const unsigned char *buffer, int index, int length)
{
	int count = 0;
	unsigned char data;
	int i = 0;

	_spi->ChipSelect(true); // Assert !CS
	_spi->Write((unsigned char) Eeprom::Commands::WriteEnable); // Write Enable
	_spi->ChipSelect(false); // Assert !CS

	_spi->ChipSelect(true); // Assert !CS
	_spi->Write((unsigned char) Eeprom::Commands::Write); // Write
	_spi->Write((unsigned char) (address >> 16)); // Address MSB
	_spi->Write((unsigned char) (address >> 8)); // Address MSB
	_spi->Write((unsigned char) address); // Address LSB

	for (i = 0; i < length; i++)
	{
		_spi->Write(buffer[i]); // Data
		if (++count <= 63)
			continue;

		count = 0;
		_spi->ChipSelect(false); // Deassert !CS

		// Wait for write to finish
		while (true)
		{
			_spi->ChipSelect(true); // Assert !CS
			_spi->Write((unsigned char) Eeprom::Commands::ReadStatusRegister); // Read Status Register
			data = _spi->Read(); // Read Byte
			_spi->ChipSelect(false); // Assert !CS

			if ((data & 1) == 0)
				break;
		}

		_spi->ChipSelect(true); // Assert !CS
		_spi->Write((unsigned char) Eeprom::Commands::WriteEnable); // Write Enable
		_spi->ChipSelect(false); // Assert !CS

		_spi->ChipSelect(true); // Assert !CS
		_spi->Write((unsigned char) Eeprom::Commands::Write); // Write
		_spi->Write((unsigned char) ((address + i + 1) >> 16)); // Address MSB
		_spi->Write((unsigned char) ((address + i + 1) >> 8)); // Address MSB
		_spi->Write((unsigned char) (address + i + 1)); // Address LSB
	}
	_spi->ChipSelect(false); // Deassert !CS

	// Wait for write to finish
	while (true)
	{
		_spi->ChipSelect(true); // Assert !CS
		_spi->Write((unsigned char) Eeprom::Commands::ReadStatusRegister); // Read Status Register
		data = _spi->Read(); // Read Byte
		_spi->ChipSelect(false); // Assert !CS

		if ((data & 1) == 0)
			break;
	}
	return;
}

/*! \brief Writes a std::string to the EEPROM starting at the specified address
 *
 * @param address Address to write byte to
 * @param data std::string to send
 */
void Eeprom::Write(UINT32 address, std::string data)
{
	this->Write(address, (const unsigned char *) data.c_str(), 0, data.length());
	this->Write(address + data.length(), 0);
	return;
}

/*! \brief Reads a single byte from the EEPROM starting at the specified address
 *
 * @param address Address to write byte to
 * @returns Byte read from EEPROM
 */
unsigned char Eeprom::Read(UINT32 address)
{
	unsigned char buffer;
	this->Read(address, &buffer, 1);
	return buffer;
}

/*! \brief Reads multiple bytes from the EEPROM starting at the specified address
 *
 * @param address Address to write byte to
 * @param buffer Pointer to the buffer to fill with received data.
 * @param length Number of bytes to receive.
 */
void Eeprom::Read(UINT32 address, unsigned char *buffer, int length)
{
	_spi->ChipSelect(true); // Assert !CS
	_spi->Write((unsigned char) Eeprom::Commands::Read); // Read
	_spi->Write((unsigned char) (address >> 16)); // Address MSB
	_spi->Write((unsigned char) (address >> 8)); // Address MSB
	_spi->Write((unsigned char) address); // Address LSB
	for (int i = 0; i < length; i++)
	{
		buffer[i] = _spi->Read();
	}
	_spi->ChipSelect(false); // Dessert !CS

	return;
}

/*! \brief Reads a std::string from the EEPROM starting at the specified address
 *
 * Reads data from the EEPROM device and and replaces the
 * contents of the buffer with the received data NOT including the std::string terminator.\n
 * This function returns when a std::string terminator is read.
 *
 * @param address Address to write byte to
 * @param buffer Pointer to the std::string buffer to fill with received data.
 */
void Eeprom::Read(UINT32 address, std::string *buffer)
{
	unsigned char data;
	int offset = 0;
	buffer->clear();

	_spi->ChipSelect(true); // Assert !CS
	_spi->Write((unsigned char) Eeprom::Commands::Read); // Read
	_spi->Write((unsigned char) (address >> 16)); // Address MSB
	_spi->Write((unsigned char) (address >> 8)); // Address MSB
	_spi->Write((unsigned char) address); // Address LSB

	while (true)
	{
		data = _spi->Read();
		*buffer += data;

		if (data == '\0')
			return;

		offset++;
	}

	return;
}